﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using DiscoveryLogic.Analytics.BusinessLogic.Importers;
using DiscoveryLogic.Analytics.DataObjects;
using DiscoveryLogic.Analytics.DataObjects.Networks;
using DiscoveryLogic.Common.Utilities;

namespace DiscoveryLogic.Analytics.BusinessLogic.Subjects
{
    public class SubjectLinkWorker
    {
        private Network _Network;
        private Dictionary<string, Journal> _Journals;
        private Dictionary<int, string> _PubJournalMappings;

        public SubjectLinkWorker(Network network)
        {
            this._Network = network;
            this.InitJournalsByPublications(this._Network);
        }

        private void InitJournalsByPublications(Network network)
        {
            List<int> pmids = new List<int>();
            foreach(EntityBase entity in network.Records)
            {
                if(entity.GetType()==typeof(PublicationDO))
                {
                    PublicationDO pub = (PublicationDO) entity;
                    pmids.Add(pub.PMID);
                }
            }
            Dictionary<string, Journal> journals = PublicationUtil.GetJournals(pmids);
            this._Journals = journals;
            this._PubJournalMappings = new Dictionary<int, string>();
            string connStr = ConfigurationManager.ConnectionStrings["WebOfScience"].ConnectionString;
            SqlConnection conn=new SqlConnection(connStr);
            try
            {
                conn.Open();
                string sql = @"
select * from Journal a where ISSN in (
select ISSN from SourceIssue where SourceIssueID in (
select SourceIssueID from SourceItem where SourceItemID in (
select SourceItemId from Medline.dbo.CitationSourceItem where PMID={0})))
order by Year desc";
                SqlCommand cmd = conn.CreateCommand();
                foreach(int pmid in pmids)
                {
                    cmd.CommandText = string.Format(sql, pmid);
                    SqlDataReader reader = cmd.ExecuteReader();
                    if(reader.Read() && reader["ISSN"] !=DBNull.Value)
                    {
                        // string journalName = reader["Title"].ToString();
                        string issn = reader["ISSN"].ToString();
                        this._PubJournalMappings.Add(pmid, issn);
                    }
                    reader.Close();
                }
            }
            catch(Exception ex)
            {
                ExceptionHandler.HandlesException(ex);
            }
            finally
            {
                conn.Close();
            }
        }

        public Network BuildSubjectLinks()
        {
            Dictionary<string, Dictionary<string,int>> subjWeights = this.InitSubjWeights(this._Journals);
            Dictionary<string, Dictionary<string, double>> subjCiteFreqs = this.PopulateSubjCiteFrequencies(this._Network);
            Dictionary<string, EntityBase> subjEntities=new Dictionary<string, EntityBase>();
            foreach(EntityBase entity in this._Network.Records)
            {
                if(entity.EntityType==EntityType.Subject)
                {
                    string subj = ((Subject) entity).Category;
                    if (!subjEntities.ContainsKey(subj))
                        subjEntities.Add(subj, entity);
                }
            }
            List<Link> subjLinks=new List<Link>();
            List<string> allSubjs = subjEntities.Keys.ToList();
            for(int i=0; i<allSubjs.Count; i++)
            {
                string fromSubj = allSubjs[i];
                if(subjCiteFreqs.ContainsKey(fromSubj))
                {
                    Dictionary<string, int> toSubjWeights = subjWeights[fromSubj];
                    Dictionary<string, double> toSubjFreqs = subjCiteFreqs[fromSubj];
                    for(int k=0;k<allSubjs.Count;k++)
                    {
                        if(k==i)
                            continue;
                        string toSubj = allSubjs[k];
                        if(!toSubjWeights.ContainsKey(toSubj))
                            continue;
                        int citeWeight = toSubjWeights[toSubj];
                        if(!toSubjFreqs.ContainsKey(toSubj))
                            continue;
                        double citeFreq = toSubjFreqs[toSubj];
                        double linkScore = citeFreq*citeWeight*citeWeight;
                        double distance = linkScore > 0 ? 1/linkScore : 1;
                        Link link = new Link(
                            typeof (Subject), typeof (Subject),
                            fromSubj, toSubj, distance, 1,
                            LinkType.Cluster, true);
                        subjLinks.Add(link);
                    }
                }
            }

            this._Network.Records = subjEntities.Values.ToList();
            this._Network.Links = subjLinks;

            return this._Network;
        }

        #region subj weights
        /// <summary>
        /// co-occurances of subject category
        /// </summary>
        /// <param name="journals"></param>
        /// <returns></returns>
        private Dictionary<string, Dictionary<string, int>> InitSubjWeights(Dictionary<string, Journal> journals)
        {
            Dictionary<string, Dictionary<string, int>> subjWeights = new Dictionary<string, Dictionary<string, int>>();
            foreach(Journal j in journals.Values)
            {
                if(j.SubjCategories !=null && j.SubjCategories.Count>0)
                {
                    List<string> subjs = j.SubjCategories;
                    for(int i=0;i<subjs.Count;i++)
                    {
                        string fromSubj = subjs[i];
                        Dictionary<string,int> toSubjs=new Dictionary<string, int>();
                        if (subjWeights.ContainsKey(fromSubj))
                            toSubjs = subjWeights[fromSubj];
                        for(int k=0;k<subjs.Count;k++)
                        {
                            string toSubj = subjs[k];
                            if (toSubjs.ContainsKey(toSubj))
                                toSubjs[toSubj] = toSubjs[toSubj] + 1;
                            else
                                toSubjs.Add(toSubj, 1);
                        }
                        if (subjWeights.ContainsKey(fromSubj))
                            subjWeights[fromSubj] = toSubjs;
                        else
                            subjWeights.Add(fromSubj, toSubjs);
                    }
                }
            }
            return subjWeights;
        }
        #endregion

        #region citation frequencies
        /// <summary>
        /// 
        /// </summary>
        /// <param name="network"></param>
        /// <returns></returns>
        private Dictionary<string, Dictionary<string, double>> PopulateSubjCiteFrequencies(Network network)
        {
            Dictionary<string, Dictionary<string, double>> citeFreqs =
                new Dictionary<string, Dictionary<string, double>>();
            double maxSubjCiteFreq = 0;
            Dictionary<int,List<int>> pubCites=new Dictionary<int, List<int>>();
            Dictionary<int,PublicationDO> pubs=new Dictionary<int, PublicationDO>();
            foreach(EntityBase entity in network.Records)
            {
                if(entity.GetType()==typeof(PublicationDO))
                {
                    PublicationDO pub = (PublicationDO) entity;
                    pubs.Add(pub.PMID, pub);
                }
            }
            foreach(Link link in network.Links)
            {
                if(link.FromType==typeof(PublicationDO) && 
                    link.ToType==typeof(PublicationDO))
                {
                    int fromPmid = int.Parse(link.FromKey);
                    int toPmid = int.Parse(link.ToKey);
                    if(pubCites.ContainsKey(fromPmid))
                    {
                        List<int> toPmids = pubCites[fromPmid];
                        if(!toPmids.Contains(toPmid))
                            toPmids.Add(toPmid);
                        pubCites[fromPmid] = toPmids;
                    }
                    else
                    {
                        List<int> toPmids=new List<int>();
                        toPmids.Add(toPmid);
                        pubCites.Add(fromPmid, toPmids);
                    }
                }
            }
            foreach(int fromPmid in pubCites.Keys)
            {
                if(!this._PubJournalMappings.ContainsKey(fromPmid))
                    continue;
                if(!this._Journals.ContainsKey(this._PubJournalMappings[fromPmid]))
                    continue;
                List<string> fromSubjs = this._Journals[this._PubJournalMappings[fromPmid]].SubjCategories;
                if(fromSubjs==null || fromSubjs.Count==0)
                    continue;
                List<int> toPmids = pubCites[fromPmid];
                foreach(int toPmid in toPmids)
                {
                    if(!this._PubJournalMappings.ContainsKey(toPmid))
                        continue;
                    if(!this._Journals.ContainsKey(this._PubJournalMappings[toPmid]))
                        continue;
                    List<string> toSubjs = this._Journals[this._PubJournalMappings[toPmid]].SubjCategories;
                    if(toSubjs==null || toSubjs.Count==0)
                        continue;
                    foreach(string fromSubj in fromSubjs)
                    {
                        Dictionary<string,double> toSubjFreqs=new Dictionary<string, double>();
                        if (citeFreqs.ContainsKey(fromSubj))
                            toSubjFreqs = citeFreqs[fromSubj];
                        foreach(string toSubj in toSubjs)
                        {
                            if (toSubjFreqs.ContainsKey(toSubj))
                                toSubjFreqs[toSubj] = toSubjFreqs[toSubj] + 1;
                            else 
                                toSubjFreqs.Add(toSubj,1);
                        }
                        if(citeFreqs.ContainsKey(fromSubj))
                            citeFreqs[fromSubj] = toSubjFreqs;
                        else 
                            citeFreqs.Add(fromSubj, toSubjFreqs);
                    }
                }
            }

            // normalize
            foreach(string fromSubj in citeFreqs.Keys)
            {
                foreach(string toSubj in citeFreqs[fromSubj].Keys)
                {
                    if (citeFreqs[fromSubj][toSubj] > maxSubjCiteFreq)
                        maxSubjCiteFreq = citeFreqs[fromSubj][toSubj];
                }
            }
            List<string> fromSubjKeys = citeFreqs.Keys.ToList();
            foreach (string fromSubj in fromSubjKeys)
            {
                Dictionary<string, double> toFreqs = citeFreqs[fromSubj];
                List<string> toSubjKeys = toFreqs.Keys.ToList();
                foreach (string toSubj in toSubjKeys)
                {
                    toFreqs[toSubj] = (double) toFreqs[toSubj]/maxSubjCiteFreq;
                }
                citeFreqs[fromSubj] = toFreqs;
            }
            
            return citeFreqs;
        }
        #endregion
    }
}
